#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#include "common.h"
#include "sem_priv.h"

// ------- Variáveis -------- //
dados_cmp cmp;
FILE *log_file;

int main(int argc, char *argv[]){
    
    // Inicializa variáveis
    srand( time(NULL) );
    log_file = fopen("log.txt", "w");       // arquivo de log
    pthread_t  esc[T_LIMIT], leit[T_LIMIT]; // threads
    int        qt_esc=0, qt_leit=0;         // contador p/ as thread
    targs      cur_targs[T_LIMIT];          // argumentos das threads
    pthread_attr_t attr;                    // para tornar as thread joinable
    void       *status;                     // para a função pthread_join
    int        rc;                          // para a função pthread_join
    cmp.estoque_index = 0;                  // indice do estoque

    int i;
    for(i=0; i<T_LIMIT; i++)
        cmp.estoque[i] = ' ';

    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

    // inicializa semafaros
    sem_init(&cmp.mutex, 0, 1);
    sem_init(&cmp.lei_sleep, 0, 0);
    sem_init(&cmp.esc_sleep, 0, 0);

    // threads
    if(argc > 1){
        if(strcmp(argv[1], "--auto") == 0){
            int rnd_t;

            for(i=0; i<T_LIMIT; i++){
                rnd_t = rand() % 2;
                cur_targs[i].sleept = rand() % 11;
                
                switch(rnd_t){
                    case GERENTE:
                        cur_targs[i].id     = qt_leit;
                        cur_targs[i].type   = GERENTE;
                        printf("Leitor   %d foi criado, e sera executado em %d segundo(s).\n", cur_targs[i].id, cur_targs[i].sleept);
                        pthread_create(&leit[qt_leit], &attr, leitor, (void*) &cur_targs[i]);
                        qt_leit++;
                        break;

                    case PRODUTOR:
                        cur_targs[i].id     = qt_esc;
                        cur_targs[i].type   = PRODUTOR;
                        printf("Escritor %d foi criado, e sera executado em %d segundo(s).\n", cur_targs[i].id, cur_targs[i].sleept);
                        pthread_create(&esc[qt_esc], &attr, escritor, (void*) &cur_targs[i]);
                        qt_esc++;
                        break;
                }
            }
        }
        // Se o argumento passado for errado
        else {
            printf("%s", how_to_use());
        }
    }
    // Caso o usuário não passe parâmetros ele terá controle sobre a criação
    // das threads
    else {
        int line_width = 20;
        char line[line_width + 1], *token, args[line_width][line_width];
        int error, nargs;

        // Mensagem básica
        printf("Abra um novo terminal e execute \"$tail -f log.txt\" para ver o log das ações\n");
        for(i=0; i<T_LIMIT; i++){
            error = nargs = 0;
            args[0][0] = args[1][0] = '\0';

            printf("%s", menu());
            fgets(line, line_width, stdin);
            line[strlen(line) - 1] = '\0';

            if(strcmp(line, "exit") == 0){
                break;
            }

            token = strtok(line, " ");
            while(token != NULL){
                strcpy(args[nargs++], token);
                token = strtok(NULL, " ");
            }
            
            if(strcmp(args[0], "p") == 0) {
                cur_targs[i].type = PRODUTOR;
            }
            else if(strcmp(args[0], "g") == 0) {
                cur_targs[i].type = GERENTE;
            }
            else {
                error = 1;
            }

            if(args[1][0] != '\0'){
                cur_targs[i].sleept = atoi(args[1]);
                if(cur_targs[i].sleept < 0 || cur_targs[i].sleept > 10) {
                    error = 1;
                }

            }
            else {
                cur_targs[i].sleept = rand() % 11;
            }

            if(error) {
                printf("Erro, use o programa segundo o menu:");
                continue;
            }

            switch(cur_targs[i].type){
                case GERENTE:
                    cur_targs[i].id = qt_leit;
                    printf("Um leitor foi criado, e sera executado em %d segundo(s).\n", cur_targs[i].sleept);
                    pthread_create(&leit[qt_leit], &attr, leitor, (void*) &cur_targs[i]);
                    qt_leit++;
                    break;

                case PRODUTOR:
                    cur_targs[i].id = qt_esc;
                    printf("Um escritor foi criado, e sera executado em %d segundo(s).\n", cur_targs[i].sleept);
                    pthread_create(&esc[qt_esc], &attr, escritor, (void*) &cur_targs[i]);
                    qt_esc++;
                    break;
            }
            fflush(stdin);

        }

    }
    printf("saindo..\n");
    // Aguarda o termino das threads
    pthread_attr_destroy(&attr);
    for(i=0; i<qt_leit; i++){
        rc = pthread_join(leit[i], &status);
        if (rc) {
            printf("ERROR; return code from pthread_join() is %d\n", rc);
            exit(-1);
        }
    }
    for(i=0; i<qt_esc; i++){
        rc = pthread_join(esc[i], &status);
        if (rc) {
            printf("ERROR; return code from pthread_join() is %d\n", rc);
            exit(-1);
        }
    }
    // fecha o arquivo
    fprintf(log_file, "Fim da execução\n");
    fclose(log_file);
    return 0;
}

void *leitor(void *arg){
    targs* struct_c = (targs*) arg;
    
    msg_ini(struct_c, log_file);

    sem_wait(&cmp.mutex);
    if(cmp.esc_wait > 0){
        cmp.lei_wait++;
        sem_post(&cmp.mutex);
        sem_wait(&cmp.lei_sleep);
    }
    else {
        cmp.lei_proc++;
        sem_post(&cmp.mutex);
    }

    acessa_area_compartilhada(struct_c, log_file, cmp.estoque, cmp.estoque_index);

    sem_wait(&cmp.mutex);
    cmp.lei_proc--;
    if(cmp.lei_proc == 0 && cmp.esc_wait > 0){
        cmp.esc_wait--;
        sem_post(&cmp.esc_sleep);
    }
    else {
        sem_post(&cmp.mutex);
    }

    pthread_exit(NULL);
}

void *escritor(void *arg){
    targs* struct_c = (targs*) arg;

    msg_ini(struct_c, log_file);

    sem_wait(&cmp.mutex);
    if(cmp.lei_proc > 0){
        cmp.esc_wait++;
        sem_post(&cmp.mutex);
        sem_wait(&cmp.esc_sleep);
    }

    acessa_area_compartilhada(struct_c, log_file, cmp.estoque, &cmp.estoque_index);

    if(cmp.lei_wait > 0){
        while(cmp.lei_wait > 0){
            cmp.lei_wait--;
            cmp.lei_proc++;
            sem_post(&cmp.lei_sleep);
        }
    }
    else {
        if(cmp.esc_wait > 0){
            cmp.esc_wait--;
            sem_post(&cmp.esc_sleep);
        }
        else {
            sem_post(&cmp.mutex);
        }
    }

    pthread_exit(NULL);
}

